package core;

import inout.TreeBuilder;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.HashMap;

import javax.swing.tree.DefaultMutableTreeNode;

import redsig.redaction.Redacter;
import redsig.signature.DocumentSigner;
import redsig.signature.SignatureImpl;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
import data.XMLNode;

/**
 * @author  Bacher
 */
public class Adapter{
	private static HashMap<DefaultMutableTreeNode, XMLNode> map = new HashMap<DefaultMutableTreeNode, XMLNode>();
	private static Redacter redacter;
	private static File currentFile;
	
	public final static String REDACTED_TAG = "red--";
	public final static String SALT_TAG = "salt--";
	
	/**
	 * Variables used for performance measurements
	 */
	public static int numSalts = 0;
	public static int numRedacts = 0;
	public static int deepestLevel = 0;
	public static long millisForRedaction = 0;
	public static long originalFileSize = 0;
	public static long redactedFileSize = 0;
	
	
	public static void printStatistics(){
		System.out.println("------- General statistics --------");
		System.out.println("# of Redacted Nodes: "+numRedacts+"\n" +
				"deepest redacted node is on level "+deepestLevel+"\n" + 
				"# of Salts used: "+numSalts+"\n" +
				"Redaction took "+millisForRedaction+" milliseconds");
		
		System.out.println("-------- File statistics ---------");
		System.out.println("Original filesize: "+originalFileSize+ "\n" +
				"Redacted filesize: "+redactedFileSize);
		
		if(originalFileSize < redactedFileSize){
			System.out.print("Size increased by "+(int)((redactedFileSize / (float) originalFileSize) * 100)+"%");
		}else{
			System.out.print("Size decreased by "+(int)((1-(redactedFileSize / (float) originalFileSize)) * 100)+"%");
		}		
	}
	
	/**
	 * @uml.property  name="treeBuilder"
	 * @uml.associationEnd  
	 */
	private static TreeBuilder treeBuilder = new TreeBuilder(null);
	private static ArrayList<DefaultMutableTreeNode> redactionQueue = new ArrayList<DefaultMutableTreeNode>();
	/**
	 * @return
	 * @uml.property  name="privateKey"
	 */
	public static PrivateKey getPrivateKey() {
		return privateKey;
	}

	/**
	 * @param privateKey
	 * @uml.property  name="privateKey"
	 */
	public static void setPrivateKey(PrivateKey privateKey) {
		Adapter.privateKey = privateKey;
	}

	/**
	 * @return
	 * @uml.property  name="certificate"
	 */
	public static Certificate getCertificate() {
		return certificate;
	}

	/**
	 * @param certificate
	 * @uml.property  name="certificate"
	 */
	public static void setCertificate(Certificate certificate) {
		Adapter.certificate = certificate;
	}

	private static PrivateKey privateKey;
	private static Certificate certificate;
	private static DocumentSigner docSigner = new DocumentSigner();
	
	public static final int OPEN_FILE_NO_SIG_FOUND = 0;
	public static final int OPEN_FILE_SIG_FOUND = 1;
	
	/**
	 * @param map
	 * @uml.property  name="map"
	 */
	public static void setMap(HashMap<DefaultMutableTreeNode, XMLNode> map) {
		Adapter.map = map;
	}

	/**
	 * @return
	 * @uml.property  name="map"
	 */
	public static HashMap<DefaultMutableTreeNode, XMLNode> getMap() {
		return map;
	}
	
	public static ArrayList<String> getNodeInfo(DefaultMutableTreeNode node){
		ArrayList<String> info = new ArrayList<String>();
		XMLNode xmlNode = map.get(node);
		info.add(xmlNode.getName());
		info.add(Boolean.toString(xmlNode.canHaveChildren()));
		info.add(Boolean.toString(xmlNode.hasAttributes()));
		info.add(Boolean.toString(xmlNode.isAttributeName()));
		info.add(Boolean.toString(xmlNode.isAttributeValue()));
		info.add(byteToBase64(xmlNode.getHashValue()));
		info.add(byteToBase64(xmlNode.getSalt()));
		return info;
	}
	
	public static String byteToBase64(byte[] data){
		BASE64Encoder encoder = new BASE64Encoder();
		String encodedString = encoder.encode(data);
		
//		char[] chars = encodedString.toCharArray();
//		  
//		StringBuffer hex = new StringBuffer();
//		for(int i = 0; i < chars.length; i++){
//		  hex.append(Integer.toHexString((int)chars[i]));
//		}
//	 
//		return hex.toString();
		return encodedString;
	}
	
	public static String byteToHexString(byte[] buf){
//		BASE64Encoder encoder = new BASE64Encoder();
//		String encodedString = encoder.encode(data);
//		
//		char[] chars = encodedString.toCharArray();
//		  
//		StringBuffer hex = new StringBuffer();
//		for(int i = 0; i < chars.length; i++){
//		  hex.append(Integer.toHexString((int)chars[i]));
//		}
//	 
//		return hex.toString();
        StringBuffer strbuf = new StringBuffer(buf.length * 2);
        int i;
        for (i = 0; i < buf.length; i++) {
            if (((int) buf[i] & 0xff) < 0x10)
                strbuf.append("0");
            strbuf.append(Long.toString((int) buf[i] & 0xff, 16));
        }
       return strbuf.toString();
	}
	
	public static byte[] base64ToByte(String data) throws IOException {
		
		BASE64Decoder decoder = new BASE64Decoder();
		return decoder.decodeBuffer(data);
	}
	
	public static byte[] hexToByte(String s) throws IOException{
        String s2;
        byte[] b = new byte[s.length() / 2];
        int i;
        for (i = 0; i < s.length() / 2; i++) {
            s2 = s.substring(i * 2, i * 2 + 2);
            b[i] = (byte)(Integer.parseInt(s2, 16) & 0xff);
        }
        return b;
//		StringBuilder sb = new StringBuilder();
//		StringBuilder temp = new StringBuilder();
//	 
//		//49204c6f7665204a617661 split into two characters 49, 20, 4c...
//		for( int i=0; i<hex.length()-1; i+=2 ){
//			//grab the hex in pairs
//		    String output = hex.substring(i, (i + 2));
//		    //convert hex to decimal
//		    int decimal = Integer.parseInt(output, 16);
//		    //convert the decimal to character
//		    sb.append((char)decimal);
//	 	    temp.append(decimal);
//		}
//		
//		return sb.toString().getBytes();
////		BASE64Decoder decoder = new BASE64Decoder();
////		return decoder.decodeBuffer(temp.toString());
	}

	
	public static void writeAll(){	
		for(XMLNode node : map.values()){
			System.out.println(node.getName()+" - "+node.getPublishSalt());
		}
	}

	/**
	 * @param currentFile
	 * @uml.property  name="currentFile"
	 */
	public static DefaultMutableTreeNode setCurrentFile(File currentFile){
		Adapter.currentFile = currentFile;
		Adapter.docSigner.setSignatureFile(null);
		redsig.signature.Signature signature = SignatureImpl.checkForSignatureFile(Adapter.currentFile);
		if(signature != null){
			docSigner.setSignatureFile(signature);
		}
	
		treeBuilder = new TreeBuilder(Adapter.currentFile);
		treeBuilder.forceBuild();
		redactionQueue.clear();

		if(signature != null && !Redacter.checkForRedaction(Adapter.getMap().get(treeBuilder.getRoot()), false)){
			SignatureImpl.hashUnredactedDocument(Adapter.getMap().get(treeBuilder.getRoot()), signature.getSalt());
		}
		return treeBuilder.getRoot();
	}
	
	public static boolean checkSignature(){
		if(Adapter.docSigner.getSignatureFile() != null)
			return true;
		else return false;
	}

	/**
	 * @return
	 * @uml.property  name="currentFile"
	 */
	public static File getCurrentFile() {
		return currentFile;
	}

	/**
	 * @return
	 * @uml.property  name="treeBuilder"
	 */
	public static TreeBuilder getTreeBuilder() {
		return treeBuilder;
	}

	/**
	 * @param treeBuilder
	 * @uml.property  name="treeBuilder"
	 */
	public static void setTreeBuilder(TreeBuilder treeBuilder) {
		Adapter.treeBuilder = treeBuilder;
	}
	
	/**
	 * @return
	 * @uml.property  name="redactionQueue"
	 */
	public static ArrayList<DefaultMutableTreeNode> getRedactionQueue() {
		return redactionQueue;
	}
	
	public void addToRedactionQueue(DefaultMutableTreeNode node){
		if(node != null){
			redactionQueue.add(node);
		}
	}
	
	public void clearRedactionQueue(){
		redactionQueue.clear();
	}
	
	public void removeFromRedactionQueue(int index){
		redactionQueue.remove(index);
	}
	
	public void removeFromRedactionQueue(Object o){
		redactionQueue.remove(o);
	}

	public static boolean isNodeInRedactionQueue(
			DefaultMutableTreeNode selectedNode) {
		return redactionQueue.contains(selectedNode);
	}

	public static void redact() {
		redacter = new Redacter(redactionQueue, treeBuilder.getRoot());
		redacter.redact();
	}

	public static void processKey(File file) {
		docSigner.processKey(file);
	}

	public static void processCertificate(File file) {
		docSigner.processCertificate(file);
	}

	public static boolean verifyDocument() {
		XMLNode rootNode = Adapter.getMap().get(treeBuilder.getRoot());
		return SignatureImpl.verifySignature(rootNode, docSigner);
	}

	public static void signDocument() {
		String filename = currentFile.getAbsolutePath().substring(0, currentFile.getAbsolutePath().length()-3) + "sig";
		File signatureFile = new File(filename);
		OutputStream out;
		try {
			out = new FileOutputStream(signatureFile);
			OutputStreamWriter osw = new OutputStreamWriter(out);
			osw.write(createSignatureFileContent(Adapter.getMap().get(treeBuilder.getRoot())));
			osw.close();
			out.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	private static String createSignatureFileContent(XMLNode rootNode){
		StringBuilder signatureFileContent = new StringBuilder();
//		System.out.println(Adapter.byteToBase64(rootNode.getHashValue()));
		docSigner.sign(rootNode.getHashValue());
		byte[] signature = docSigner.getSig();
		signatureFileContent.append("Signature: \n" + Adapter.byteToBase64(signature)+"\n");
		if(!Redacter.checkForRedaction(rootNode, false)){
			signatureFileContent.append("Seed: \n" + Adapter.byteToBase64(rootNode.getSalt()));
		}
		return signatureFileContent.toString();
	}

	public static DocumentSigner getDocSigner() {
		return docSigner;
	}	
	
}
